package zack.project.infrastructure.persistent.repository;

import cn.bugstack.middleware.db.router.strategy.IDBRouterStrategy;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RLock;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.support.TransactionTemplate;
import zack.project.domain.activity.adapter.repository.IActivityRepository;
import zack.project.domain.activity.event.ActivitySkuStockZeroMessageEvent;
import zack.project.domain.activity.model.aggregate.CreatePartakeOrderAggregate;
import zack.project.domain.activity.model.aggregate.CreateQuotaOrderAggregate;
import zack.project.domain.activity.model.entity.*;
import zack.project.domain.activity.model.valobj.ActivitySkuStockKeyVO;
import zack.project.domain.activity.model.valobj.ActivityStateVO;
import zack.project.domain.activity.model.valobj.UserRaffleOrderStateVO;
import zack.project.infrastructure.event.EventPublisher;
import zack.project.infrastructure.persistent.dao.*;
import zack.project.infrastructure.persistent.po.*;
import zack.project.infrastructure.persistent.redis.IRedisService;
import zack.project.types.common.Constants;
import zack.project.types.enums.ResponseCode;
import zack.project.types.exception.AppException;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author A1793
 */
@Slf4j
@Repository
public class ActivityRepository implements IActivityRepository {


    @Resource
    private IRedisService redisService;
    @Resource
    private IRaffleActivitySkuDao activitySkuDao;
    @Resource
    private IRaffleActivityDao activityDao;
    @Resource
    private IRaffleActivityCountDao activityCountDao;
    @Resource
    private IRaffleActivityAccountDao activityAccountDao;
    @Resource
    private IRaffleActivityOrderDao activityOrderDao;
    @Resource
    private IUserRaffleOrderDao userRaffleOrderDao;
    @Resource
    private IRaffleActivityAccountDayDao activityAccountDayDao;
    @Resource
    private IRaffleActivityAccountMonthDao activityAccountMonthDao;
    @Resource
    private IUserCreditAccountDao userCreditAccountDao;
    @Resource
    private TransactionTemplate transactionTemplate;
    @Resource
    private IDBRouterStrategy dbRouter;
    @Resource
    private ActivitySkuStockZeroMessageEvent activitySkuStockZeroMessageEvent;
    @Resource
    private EventPublisher eventPublisher;

    @Override
    public ActivitySkuEntity queryActivitySku(Long sku) {
        RaffleActivitySku raffleActivitySku = activitySkuDao.queryActivitySku(sku);
        return ActivitySkuEntity.builder()
                .sku(raffleActivitySku.getSku())
                .activityId(raffleActivitySku.getActivityId())
                .activityCountId(raffleActivitySku.getActivityCountId())
                .stockCount(raffleActivitySku.getStockCount())
                .stockCountSurplus(raffleActivitySku.getStockCountSurplus())
                .productAmount(raffleActivitySku.getPayAmount())
                .build();
    }

    @Override
    public ActivityEntity queryRaffleActivityByActivityId(Long activityId) {

        String cacheKey = Constants.RedisKey.ACTIVITY_KEY + activityId;
        ActivityEntity activityEntity = redisService.getValue(cacheKey);
        if (activityEntity != null) {
            return activityEntity;
        }
        RaffleActivity raffleActivity = activityDao.queryRaffleActivityByActivityId(activityId);
        ActivityEntity build = ActivityEntity.builder()
                .activityId(raffleActivity.getActivityId())
                .activityName(raffleActivity.getActivityName())
                .activityDesc(raffleActivity.getActivityDesc())
                .beginDateTime(raffleActivity.getBeginDateTime())
                .endDateTime(raffleActivity.getEndDateTime())
                .strategyId(raffleActivity.getStrategyId())
                .state(ActivityStateVO.valueOf(raffleActivity.getState()))
                .build();
        redisService.setValue(cacheKey, build);
        return build;
    }

    @Override
    public ActivityCountEntity queryRaffleActivityCountByActivityCountId(Long activityCountId) {
        String cacheKey = Constants.RedisKey.ACTIVITY_COUNT_KEY + activityCountId;
        ActivityCountEntity activityCountEntity = redisService.getValue(cacheKey);
        if (activityCountEntity != null) {
            return activityCountEntity;
        }
        RaffleActivityCount raffleActivityCount = activityCountDao.queryRaffleActivityCountByActivityCountId(activityCountId);
        ActivityCountEntity build = ActivityCountEntity.builder()
                .activityCountId(raffleActivityCount.getActivityCountId())
                .totalCount(raffleActivityCount.getTotalCount())
                .dayCount(raffleActivityCount.getDayCount())
                .monthCount(raffleActivityCount.getMonthCount())
                .build();
        redisService.setValue(cacheKey, build);
        return build;

    }

    @Override
    public void doSaveCreditPayOrder(CreateQuotaOrderAggregate createOrderAggregate) {
        try {
            // 创建交易订单
            ActivityOrderEntity activityOrderEntity = createOrderAggregate.getActivityOrderEntity();
            RaffleActivityOrder raffleActivityOrder = new RaffleActivityOrder();
            raffleActivityOrder.setUserId(activityOrderEntity.getUserId());
            raffleActivityOrder.setSku(activityOrderEntity.getSku());
            raffleActivityOrder.setActivityId(activityOrderEntity.getActivityId());
            raffleActivityOrder.setActivityName(activityOrderEntity.getActivityName());
            raffleActivityOrder.setStrategyId(activityOrderEntity.getStrategyId());
            raffleActivityOrder.setOrderId(activityOrderEntity.getOrderId());
            raffleActivityOrder.setOrderTime(activityOrderEntity.getOrderTime());
            //设置充值抽奖次数的配置
            raffleActivityOrder.setTotalCount(activityOrderEntity.getTotalCount());
            raffleActivityOrder.setDayCount(activityOrderEntity.getDayCount());
            raffleActivityOrder.setMonthCount(activityOrderEntity.getMonthCount());
            raffleActivityOrder.setTotalCount(createOrderAggregate.getTotalCount());
            raffleActivityOrder.setDayCount(createOrderAggregate.getDayCount());
            raffleActivityOrder.setMonthCount(createOrderAggregate.getMonthCount());
            raffleActivityOrder.setPayAmount(activityOrderEntity.getPayAmount());
            raffleActivityOrder.setState(activityOrderEntity.getState().getCode());
            raffleActivityOrder.setOutBusinessNo(activityOrderEntity.getOutBusinessNo());

            // 以用户ID作为切分键,通过 doRouter 设定路由【这样就保证了下面的操作,都是同一个链接下,也就保证了事务的特性】
            dbRouter.doRouter(createOrderAggregate.getUserId());

            // 编程式事务
            transactionTemplate.execute(status -> {
                try {
                    activityOrderDao.insert(raffleActivityOrder);
                    return 1;
                } catch (DuplicateKeyException e) {
                    status.setRollbackOnly();
                    log.error("写入订单记录,唯一索引冲突 userId: {} activityId: {} sku: {}", activityOrderEntity.getUserId(), activityOrderEntity.getActivityId(), activityOrderEntity.getSku(), e);
                    throw new AppException(ResponseCode.INDEX_DUP.getCode(), e);
                }
            });
        } finally {
            dbRouter.clear();
        }
    }


    @Override
    public void cacheActivitySkuStockCount(String cacheKey, Integer stockCount) {
        if (redisService.isExists(cacheKey)) {
            return;
        }
        redisService.setAtomicLong(cacheKey, stockCount);
    }

    @Override
    public boolean subtractionActivitySkuStock(Long sku, String cacheKey, Date endDateTime) {
        long surplus = redisService.decr(cacheKey);
        if (surplus < 0) {
            redisService.setAtomicLong(cacheKey, 0);
            return false;
        }
        //当sku商品库存为0时发一个ActivitySkuStockZeroMessageEvent消息，
        // ActivitySkuStockZeroCustomer监听并消息，
        // 将数据库表{raffle_activity_sku}中对应的商品调零并清空redis的中key为:"activity_sku_count_query_key"的消息队列清空
        if (surplus == 0) {
            eventPublisher.publish(activitySkuStockZeroMessageEvent.topic(),
                    activitySkuStockZeroMessageEvent.buildEventMessage(sku));
        }
        String lockKey = cacheKey + Constants.UNDERLINE + surplus;
        Long exprTime = endDateTime.getTime() -
                System.currentTimeMillis() +
                TimeUnit.DAYS.toMillis(1);
        //加一个sku库存抢占锁
        Boolean lock = redisService.setNx(lockKey, exprTime, TimeUnit.MILLISECONDS);
        if (!lock) {
            log.info("活动sku库存加锁失败 {}", lockKey);

        }
        return lock;


    }

    @Override
    public void activitySkuStockConsumeSendQueue(ActivitySkuStockKeyVO activitySkuStockKeyVO) {
        String cacheKey = Constants.RedisKey.ACTIVITY_SKU_COUNT_QUERY_KEY;
        RBlockingQueue<ActivitySkuStockKeyVO> blockingQueue = redisService.getBlockingQueue(cacheKey);
        RDelayedQueue<ActivitySkuStockKeyVO> delayedQueue = redisService.getDelayedQueue(blockingQueue);
        delayedQueue.offer(activitySkuStockKeyVO, 3, TimeUnit.SECONDS);

    }

    @Override
    public ActivitySkuStockKeyVO takeQueueValue() {
        String cacheKey = Constants.RedisKey.ACTIVITY_SKU_COUNT_QUERY_KEY;
        RBlockingQueue<ActivitySkuStockKeyVO> blockingQueue = redisService.getBlockingQueue(cacheKey);
        return blockingQueue.poll();
    }


    /**
     * 当通过阻塞队列获得到延时3队列时,向延时队列添加数据；
     * 当要清空延时队列时先通过阻塞队列获得延时队列,先清除延时队列,再清除阻塞队列
     */
    @Override
    public void clearQueueValue() {
        String cacheKey = Constants.RedisKey.ACTIVITY_SKU_COUNT_QUERY_KEY;
        RBlockingQueue<Object> blockingQueue = redisService.getBlockingQueue(cacheKey);
        RDelayedQueue<Object> delayedQueue = redisService.getDelayedQueue(blockingQueue);
        delayedQueue.clear();
        blockingQueue.clear();

    }

    @Override
    public void updateActivitySkuStock(Long sku) {
        activitySkuDao.updateActivitySkuStock(sku);
    }

    @Override
    public void clearActivitySkuStock(Long sku) {
        activitySkuDao.clearActivitySkuStock(sku);
    }

    /**
     * 根据用户id和活动id查询{user_raffle_order}表是否对应的状态为create的抽奖单,没有则返回空
     *
     * @param partakeRaffleActivityEntity
     * @return
     */
    @Override
    public UserRaffleOrderEntity queryNoUsedRaffleOrder(PartakeRaffleActivityEntity partakeRaffleActivityEntity) {

        UserRaffleOrder userRaffleOrderReq = new UserRaffleOrder();
        userRaffleOrderReq.setUserId(partakeRaffleActivityEntity.getUserId());
        userRaffleOrderReq.setActivityId(partakeRaffleActivityEntity.getActivityId());
        UserRaffleOrder userRaffleOrderRes = userRaffleOrderDao.queryNoUsedRaffleOrder(userRaffleOrderReq);
        if (null == userRaffleOrderRes) {
            return null;
        }
        return UserRaffleOrderEntity.builder().userId(userRaffleOrderRes.getUserId()).activityId(userRaffleOrderRes.getActivityId()).activityName(userRaffleOrderRes.getActivityName()).strategyId(userRaffleOrderRes.getStrategyId()).orderId(userRaffleOrderRes.getOrderId()).orderTime(userRaffleOrderRes.getOrderTime()).orderState(UserRaffleOrderStateVO.valueOf(userRaffleOrderRes.getOrderState()))

                .build();
    }

    /**
     * 对user_raffle_order,account,account_day,account_month,activity_account表进行事务操作
     *
     * @param createPartakeOrderAggregate
     */
    @Override
    public void saveCreatePartakeOrderAggregate(CreatePartakeOrderAggregate createPartakeOrderAggregate) {
        try {
            //获取聚合对象里的所有内容用来后续一个事务内写入
            String userId = createPartakeOrderAggregate.getUserId();
            Long activityId = createPartakeOrderAggregate.getActivityId();
            //获得账户相关实体
            ActivityAccountDayEntity activityAccountDayEntity =
                    createPartakeOrderAggregate.getActivityAccountDayEntity();
            ActivityAccountMonthEntity activityAccountMonthEntity =
                    createPartakeOrderAggregate.getActivityAccountMonthEntity();
            ActivityAccountEntity activityAccountEntity =
                    createPartakeOrderAggregate.getActivityAccountEntity();
            //获得新生成的抽奖单
            UserRaffleOrderEntity userRaffleOrderEntity = createPartakeOrderAggregate.getUserRaffleOrderEntity();
            //获取路由,接下来的事务操作要在同一个路由下进行,
            // 因为经过分库分表后,用户的总账户,日账户,月账户应该在同一个数据库下,这样子方便后续存储和查询,
            // 所以需要给查询用户总账户的dao方法添加路由

            dbRouter.doRouter(userId);
            transactionTemplate.execute(status -> {
                try {
                    //扣减总账户的总抽奖次数余量
                    int totalCount =
                            activityAccountDao.updateActivityAccountSubtractionQuota(RaffleActivityAccount.builder()
                                    .userId(userId)
                                    .activityId(activityId).build());
                    if (totalCount != 1) {
                        status.setRollbackOnly();
                        log.warn("写入创建参与活动记录,更新总账户额度不足,异常 userId: {} activityId: {}", userId, activityId);
                        throw new AppException(ResponseCode.ACCOUNT_QUOTA_ERROR.getCode(), ResponseCode.ACCOUNT_QUOTA_ERROR.getInfo());
                    }
                    /**
                     * 日账户和月账户的更新逻辑:
                     * 当不存在时,会将新生成的实体对象的余量-1再插入,因为该对象的余量是根据总对象的实体的余量获得的,
                     * 它保存的是前一个月或前一天的余量
                     * 当存在时,会在对应的账户上直接扣减余量,走的是update
                     */
                    //更新月账户,如果不存在月账户则新插入一个
                    if (createPartakeOrderAggregate.isExistAccountMonth()) {
                        //数据库表{activity_account_month}存在该用户的月账户,扣减月账户次数余量
                        int monthCount = activityAccountMonthDao.updateActivityAccountMonthSubtractionQuota(RaffleActivityAccountMonth.builder()
                                .userId(userId)
                                .activityId(activityId)
                                .month(activityAccountMonthEntity.getMonth())
                                .build());
                        if (monthCount != 1) {
                            status.setRollbackOnly();
                            log.warn("写入创建参与活动记录,更新月账户额度不足,异常 userId: {} activityId: {} month: {}", userId, activityId, activityAccountMonthEntity.getMonth());
                            throw new AppException(ResponseCode.ACCOUNT_MONTH_QUOTA_ERROR.getCode(), ResponseCode.ACCOUNT_MONTH_QUOTA_ERROR.getInfo());
                        }
                        //先更新完表{activity_account_month}之后再更新总账户的库表{raffle_activity_account}
                        activityAccountDao.updateActivityAccountMonthSubtractionQuota(RaffleActivityAccount.builder().activityId(activityId).userId(userId).build());
                    } else {
                        //新建一个月账户
                        activityAccountMonthDao.insertActivityAccountMonth(RaffleActivityAccountMonth.builder()
                                .userId(userId)
                                .activityId(activityId)
                                .month(activityAccountMonthEntity.getMonth())
                                .monthCount(activityAccountMonthEntity.getMonthCount())
                                .monthCountSurplus(activityAccountMonthEntity.getMonthCountSurplus() - 1).build());
                        //更新总账户的月额度镜像
                        activityAccountDao.updateActivityAccountMonthSurplusImageQuota(RaffleActivityAccount.builder()
                                .userId(userId)
                                .activityId(activityId)
                                .monthCountSurplus(activityAccountEntity.getMonthCountSurplus())
                                .build());
                    }
                    if (createPartakeOrderAggregate.isExistAccountDay()) {
                        //先更新日账户表
                        int dayCount = activityAccountDayDao.updateActivityAccountDaySubtractionQuota(RaffleActivityAccountDay.builder().userId(userId).activityId(activityId).day(activityAccountDayEntity.getDay()).build());
                        if (dayCount != 1) {
                            status.setRollbackOnly();
                            log.warn("写入创建参与活动记录,更新日账户额度不足,异常 userId: {} activityId: {} day: {}", userId, activityId, activityAccountDayEntity.getDay());
                            throw new AppException(ResponseCode.ACCOUNT_DAY_QUOTA_ERROR.getCode(), ResponseCode.ACCOUNT_DAY_QUOTA_ERROR.getInfo());
                        }
                        //日账户表更新成功后更新总账户表
                        activityAccountDao.updateActivityAccountDaySubtractionQuota(RaffleActivityAccount.builder().activityId(activityId).userId(userId).build());
                    } else {
                        //新建一个日账户
                        activityAccountDayDao.insertActivityAccountDay(RaffleActivityAccountDay.builder()
                                .userId(userId)
                                .activityId(activityId)
                                .day(activityAccountDayEntity.getDay())
                                .dayCount(activityAccountDayEntity.getDayCount())
                                .dayCountSurplus(activityAccountDayEntity.getDayCountSurplus() - 1).build());
                        //更新总账户的日额度镜像
                        activityAccountDao.updateActivityAccountDaySurplusImageQuota(RaffleActivityAccount.builder().userId(userId).activityId(activityId).dayCountSurplus(activityAccountEntity.getDayCountSurplus()).build());
                    }
                    //账户余量全部更新完毕后,插入新的未被使用的抽奖单
                    userRaffleOrderDao.insert(UserRaffleOrder.builder()
                            .userId(userRaffleOrderEntity.getUserId())
                            .activityId(userRaffleOrderEntity.getActivityId())
                            .activityName(userRaffleOrderEntity.getActivityName())
                            .strategyId(userRaffleOrderEntity.getStrategyId())
                            .orderId(userRaffleOrderEntity.getOrderId())
                            .orderTime(userRaffleOrderEntity.getOrderTime())
                            .orderState(userRaffleOrderEntity.getOrderState().getCode())
                            .build());
                    return 1;
                } catch (Exception e) {
                    status.setRollbackOnly();
                    log.error("写入创建参与活动记录,唯一索引冲突 userId: {} activityId: {}", userId, activityId, e);
                    throw new AppException(ResponseCode.INDEX_DUP.getCode(), e);
                }

            });
        } finally {
            dbRouter.clear();
        }
    }

    @Override
    public ActivityAccountEntity queryActivityAccountByUserId(String userId, Long activityId) {
        RaffleActivityAccount raffleActivityAccountReq = new RaffleActivityAccount();
        raffleActivityAccountReq.setUserId(userId);
        raffleActivityAccountReq.setActivityId(activityId);
        RaffleActivityAccount activityAccount = activityAccountDao.queryActivityAccountByUserId(raffleActivityAccountReq);
        if (activityAccount == null) {
            return null;
        }
        return ActivityAccountEntity.builder()
                .userId(activityAccount.getUserId())
                .activityId(activityAccount.getActivityId())
                .totalCount(activityAccount.getTotalCount()).
                totalCountSurplus(activityAccount.getTotalCountSurplus())
                .dayCount(activityAccount.getDayCount())
                .dayCountSurplus(activityAccount.getDayCountSurplus())
                .monthCount(activityAccount.getMonthCount())
                .monthCountSurplus(activityAccount.getMonthCountSurplus())
                .build();
    }

    @Override
    public ActivityAccountMonthEntity queryActivityAccountMonthByUserId(String userId, Long activityId, String month) {
        RaffleActivityAccountMonth raffleActivityAccountMonthReq = new RaffleActivityAccountMonth();
        raffleActivityAccountMonthReq.setUserId(userId);
        raffleActivityAccountMonthReq.setActivityId(activityId);
        raffleActivityAccountMonthReq.setMonth(month);
        RaffleActivityAccountMonth raffleActivityAccountMonthRes = activityAccountMonthDao.queryActivityAccountMonthByUserId(raffleActivityAccountMonthReq);
        if (null == raffleActivityAccountMonthRes) {
            return null;
        }
        // 2. 转换对象
        return ActivityAccountMonthEntity.builder().userId(raffleActivityAccountMonthRes.getUserId()).activityId(raffleActivityAccountMonthRes.getActivityId()).month(raffleActivityAccountMonthRes.getMonth()).monthCount(raffleActivityAccountMonthRes.getMonthCount()).monthCountSurplus(raffleActivityAccountMonthRes.getMonthCountSurplus()).build();
    }

    @Override
    public ActivityAccountDayEntity queryActivityAccountDayByUserId(String userId, Long activityId, String day) {
        // 1. 查询账户
        RaffleActivityAccountDay raffleActivityAccountDayReq = new RaffleActivityAccountDay();
        raffleActivityAccountDayReq.setUserId(userId);
        raffleActivityAccountDayReq.setActivityId(activityId);
        raffleActivityAccountDayReq.setDay(day);
        RaffleActivityAccountDay raffleActivityAccountDayRes = activityAccountDayDao.queryActivityAccountDayByUserId(raffleActivityAccountDayReq);
        if (null == raffleActivityAccountDayRes) {
            return null;
        }
        // 2. 转换对象
        return ActivityAccountDayEntity.builder().userId(raffleActivityAccountDayRes.getUserId()).activityId(raffleActivityAccountDayRes.getActivityId()).day(raffleActivityAccountDayRes.getDay()).dayCount(raffleActivityAccountDayRes.getDayCount()).dayCountSurplus(raffleActivityAccountDayRes.getDayCountSurplus()).build();
    }

    @Override
    public List<ActivitySkuEntity> queryActivitySkuListByActivityId(Long activityId) {
        List<RaffleActivitySku> skus = activitySkuDao.queryActivitySkuListByActivityId(activityId);
        List<ActivitySkuEntity> skuEntities = new ArrayList<ActivitySkuEntity>(skus.size());

        for (RaffleActivitySku sku : skus) {

            ActivitySkuEntity activitySkuEntity = ActivitySkuEntity.builder()
                    .sku(sku.getSku())
                    .activityId(sku.getActivityId())
                    .activityCountId(sku.getActivityCountId())
                    .stockCount(sku.getStockCount())
                    .stockCountSurplus(sku.getStockCountSurplus())
                    .productAmount(sku.getPayAmount()).build();
            skuEntities.add(activitySkuEntity);
        }
        return skuEntities;

    }

    @Override
    public Integer queryRaffleActivityAccountDayPartakeCount(Long activityId, String userId) {
        RaffleActivityAccountDay raffleActivityAccountDay = new RaffleActivityAccountDay();
        raffleActivityAccountDay.setUserId(userId);
        raffleActivityAccountDay.setActivityId(activityId);
        raffleActivityAccountDay.setDay(RaffleActivityAccountDay.curDay());
        Integer count = activityAccountDayDao.queryRaffleActivityAccountDayPartakeCount(raffleActivityAccountDay);
        return count == null ? 0 : count;
    }

    @Override
    public ActivityAccountEntity queryActivityAccountEntity(String userId, Long activityId) {
        RaffleActivityAccount activityAccount = activityAccountDao.queryActivityAccountByUserId(RaffleActivityAccount.builder().userId(userId).activityId(activityId).build());

        if (null == activityAccount) {
            return ActivityAccountEntity.builder().activityId(activityId).userId(userId).dayCount(0).dayCountSurplus(0).monthCount(0).monthCountSurplus(0).totalCount(0).totalCountSurplus(0).build();
        }

        RaffleActivityAccountMonth raffleActivityAccountMonth = activityAccountMonthDao.queryActivityAccountMonthByUserId(RaffleActivityAccountMonth.builder().userId(userId).activityId(activityId).build());

        RaffleActivityAccountDay raffleActivityAccountDay = activityAccountDayDao.queryActivityAccountDayByUserId(RaffleActivityAccountDay.builder().userId(userId).activityId(activityId).build());

        ActivityAccountEntity activityAccountEntity = new ActivityAccountEntity();
        activityAccountEntity.setUserId(userId);
        activityAccountEntity.setActivityId(activityId);
        activityAccountEntity.setTotalCount(activityAccount.getTotalCount());
        activityAccountEntity.setTotalCountSurplus(activityAccount.getTotalCountSurplus());

        if (raffleActivityAccountMonth == null) {
            activityAccountEntity.setMonthCount(activityAccount.getMonthCount());
            activityAccountEntity.setMonthCountSurplus(activityAccount.getMonthCountSurplus());
        } else {
            activityAccountEntity.setMonthCount(raffleActivityAccountMonth.getMonthCount());
            activityAccountEntity.setMonthCountSurplus(raffleActivityAccountMonth.getMonthCountSurplus());
        }
        if (raffleActivityAccountDay == null) {
            activityAccountEntity.setDayCount(activityAccount.getDayCount());
            activityAccountEntity.setDayCountSurplus(activityAccount.getDayCountSurplus());
        } else {
            activityAccountEntity.setDayCount(raffleActivityAccountDay.getDayCount());
            activityAccountEntity.setDayCountSurplus(raffleActivityAccountDay.getDayCountSurplus());
        }

        return activityAccountEntity;

    }

    @Override
    public Integer queryRaffleActivityAccountPartakeCount(Long activityId, String userId) {
        ;
        RaffleActivityAccount activityAccount =
                activityAccountDao.queryActivityAccountByUserId(RaffleActivityAccount.builder().userId(userId).activityId(activityId).build());

        if (null == activityAccount) {
            return 0;
        }
        return activityAccount.getTotalCount() - activityAccount.getTotalCountSurplus();
    }

    /**
     * 在成功获取锁("activity_account_lock_#{userId}_#{activityId}")之后,保存充值单({修改库表{raffle_activity_order}})
     * 并修改用户的总,日,月账户的抽奖次数(修改库表{raffle_activity_account},{{raffle_activity_account_day},{raffle_activity_account_month}})
     *
     * @param createQuotaOrderAggregate
     */
    @Override
    public void doSaveNoPayOrder(CreateQuotaOrderAggregate createQuotaOrderAggregate) {
        //在给用户账户充值抽奖次数前先在缓存设置一个分布式锁
        RLock lock = redisService.getLock(Constants.RedisKey.ACTIVITY_ACCOUNT_LOCK +
                createQuotaOrderAggregate.getUserId() +
                Constants.UNDERLINE +
                createQuotaOrderAggregate.getActivityId());

        try {
            //抢占到锁的执行数据库修改逻辑
            //未抢占到的会阻塞3秒，当锁释放后执行修改逻辑，如果上一个抢占到锁的应用修改数据库成功这里则会报错，未修改成功这里会重新修改。
            lock.lock(3, TimeUnit.SECONDS);

            ActivityOrderEntity activityOrderEntity =
                    createQuotaOrderAggregate.getActivityOrderEntity();
            RaffleActivityOrder raffleActivityOrderReq = new RaffleActivityOrder();
            raffleActivityOrderReq.setUserId(activityOrderEntity.getUserId());
            raffleActivityOrderReq.setSku(activityOrderEntity.getSku());
            raffleActivityOrderReq.setActivityId(activityOrderEntity.getActivityId());
            raffleActivityOrderReq.setActivityName(activityOrderEntity.getActivityName());
            raffleActivityOrderReq.setStrategyId(activityOrderEntity.getStrategyId());
            raffleActivityOrderReq.setOrderId(activityOrderEntity.getOrderId());
            raffleActivityOrderReq.setOrderTime(activityOrderEntity.getOrderTime());
            raffleActivityOrderReq.setTotalCount(activityOrderEntity.getTotalCount());
            raffleActivityOrderReq.setDayCount(activityOrderEntity.getDayCount());
            raffleActivityOrderReq.setMonthCount(activityOrderEntity.getMonthCount());
            raffleActivityOrderReq.setState(activityOrderEntity.getState().getCode());
            raffleActivityOrderReq.setOutBusinessNo(activityOrderEntity.getOutBusinessNo());

            RaffleActivityAccount activityAccountReq = new RaffleActivityAccount();
            activityAccountReq.setUserId(activityOrderEntity.getUserId());
            activityAccountReq.setActivityId(activityOrderEntity.getActivityId());
            activityAccountReq.setTotalCount(activityOrderEntity.getTotalCount());
            activityAccountReq.setTotalCountSurplus(activityOrderEntity.getTotalCount());
            activityAccountReq.setDayCount(activityOrderEntity.getDayCount());
            activityAccountReq.setDayCountSurplus(activityOrderEntity.getDayCount());

            activityAccountReq.setMonthCount(activityOrderEntity.getMonthCount());
            activityAccountReq.setMonthCountSurplus(activityOrderEntity.getMonthCount());


            RaffleActivityAccountDay raffleActivityAccountDayReq = new RaffleActivityAccountDay();
            raffleActivityAccountDayReq.setUserId(activityOrderEntity.getUserId());
            raffleActivityAccountDayReq.setActivityId(activityOrderEntity.getActivityId());
            raffleActivityAccountDayReq.setDayCount(activityOrderEntity.getDayCount());
            raffleActivityAccountDayReq.setDayCountSurplus(activityOrderEntity.getDayCount());


            RaffleActivityAccountMonth raffleActivityAccountMonthReq = new RaffleActivityAccountMonth();
            raffleActivityAccountMonthReq.setUserId(activityOrderEntity.getUserId());
            raffleActivityAccountMonthReq.setActivityId(activityOrderEntity.getActivityId());
            raffleActivityAccountMonthReq.setMonthCount(activityOrderEntity.getMonthCount());
            raffleActivityAccountMonthReq.setMonthCountSurplus(activityOrderEntity.getMonthCount());

            dbRouter.doRouter(activityAccountReq.getUserId());
            transactionTemplate.execute(status -> {
                try {
                    //插入一笔新的活动订单记录
                    activityOrderDao.insert(raffleActivityOrderReq);

                    //更新用户的总,月,日账户的抽奖次数
                    RaffleActivityAccount activityAccount =
                            activityAccountDao.queryActivityAccountByUserId(activityAccountReq);
                    if (null == activityAccount) {
                        activityAccountDao.insert(activityAccountReq);
                    } else {
                        activityAccountDao.updateAccountQuota(activityAccountReq);
                    }
                    activityAccountDayDao.addAccountQuota(raffleActivityAccountDayReq);
                    activityAccountMonthDao.addAccountQuota(raffleActivityAccountMonthReq);
                    return 1;
                }
                catch (DuplicateKeyException e) {
                    status.setRollbackOnly();
                    log.error("写入订单记录,唯一索引冲突 userId: {} activityId: {} sku: {}", activityOrderEntity.getUserId(), activityOrderEntity.getActivityId(), activityOrderEntity.getSku(), e);
                    throw new AppException(ResponseCode.INDEX_DUP.getCode(), e);
                }
            });
        } finally {
            dbRouter.clear();
            lock.unlock();
        }
    }

    @Override
    public void updateOrder(DeliveryOrderEntity deliveryOrderEntity) {
        //再调整用户抽奖次数之前先在缓存中加一个锁:key(activity_account_update_lock_#{userId})
        //mq消息执行要加一个分布式锁，防止消息被不同应用多次执行
        RLock lock = redisService.getLock(Constants.RedisKey.ACTIVITY_ACCOUNT_UPDATE_LOCK +
                deliveryOrderEntity.getUserId());
        try {
            lock.lock(3, TimeUnit.SECONDS);

            RaffleActivityOrder raffleActivityOrderReq = new RaffleActivityOrder();
            raffleActivityOrderReq.setUserId(deliveryOrderEntity.getUserId());
            raffleActivityOrderReq.setOutBusinessNo(deliveryOrderEntity.getOutBusinessNo());
            RaffleActivityOrder raffleActivityOrderRes =
                    activityOrderDao.queryRaffleActivityOrder(raffleActivityOrderReq);
            //如果此处为空说明是用户行为返利增加的积分调额成功的消息被CreditAdjustSuccessCustomer消费，
            //返利获得的积分在creditService中插入了积分订单和更新积分账户并发送积分调额成功的消息，
            // 没有操作raffle_activity_order
            if (null == raffleActivityOrderRes) {
                if (lock.isLocked()) {
                    lock.unlock();
                    return;
                }
            }

            RaffleActivityAccount activityAccountReq = new RaffleActivityAccount();
            activityAccountReq.setUserId(raffleActivityOrderRes.getUserId());
            activityAccountReq.setActivityId(raffleActivityOrderRes.getActivityId());
            activityAccountReq.setTotalCount(raffleActivityOrderRes.getTotalCount());
            activityAccountReq.setDayCount(raffleActivityOrderRes.getDayCount());
            activityAccountReq.setMonthCount(raffleActivityOrderRes.getMonthCount());
            activityAccountReq.setTotalCountSurplus(raffleActivityOrderRes.getTotalCount());
            activityAccountReq.setDayCountSurplus(raffleActivityOrderRes.getDayCount());
            activityAccountReq.setMonthCountSurplus(raffleActivityOrderRes.getMonthCount());

            RaffleActivityAccountDay raffleActivityAccountDay = new RaffleActivityAccountDay();
            raffleActivityAccountDay.setUserId(raffleActivityOrderRes.getUserId());
            raffleActivityAccountDay.setActivityId(raffleActivityOrderRes.getActivityId());
            raffleActivityAccountDay.setDayCount(raffleActivityOrderRes.getDayCount());
            raffleActivityAccountDay.setDayCountSurplus(raffleActivityOrderRes.getDayCount());
            raffleActivityAccountDay.setDay(RaffleActivityAccountDay.curDay());

            RaffleActivityAccountMonth raffleActivityAccountMonth = new RaffleActivityAccountMonth();
            raffleActivityAccountMonth.setUserId(raffleActivityOrderRes.getUserId());
            raffleActivityAccountMonth.setActivityId(raffleActivityOrderRes.getActivityId());
            raffleActivityAccountMonth.setMonth(RaffleActivityAccountMonth.currentMonth());
            raffleActivityAccountMonth.setMonthCount(raffleActivityOrderRes.getMonthCount());
            raffleActivityAccountMonth.setMonthCountSurplus(raffleActivityOrderRes.getMonthCount());

            dbRouter.doRouter(deliveryOrderEntity.getUserId());

            transactionTemplate.execute(status -> {

                try {
                    //将{raffle_activity_order}中对用的记录更新为completed，标记着充值单被使用
                    int update = activityOrderDao.updateOrderCompleted(raffleActivityOrderReq);
                    if (update != 1) {
                        log.error("无法更新流水状态");
                        status.setRollbackOnly();
                        return 1;
                    }
                    //更新账户抽奖次数
                    RaffleActivityAccount activityAccount =
                            activityAccountDao.queryActivityAccountByUserId(activityAccountReq);
                    if (null == activityAccount) {
                        activityAccountDao.insert(activityAccountReq);
                    } else {
                        activityAccountDao.updateAccountQuota(activityAccountReq);
                    }
                    activityAccountDayDao.addAccountQuota(raffleActivityAccountDay);
                    activityAccountMonthDao.addAccountQuota(raffleActivityAccountMonth);
                    return 1;

                } catch (DuplicateKeyException e) {
                    status.setRollbackOnly();
                    log.error("更新订单记录,完成态,唯一索引冲突 userId: {} outBusinessNo: {}", deliveryOrderEntity.getUserId(), deliveryOrderEntity.getOutBusinessNo(), e);
                    throw new AppException(ResponseCode.INDEX_DUP.getCode(), e);
                }
            });
        } finally {
            if (lock.isLocked()) {
                lock.unlock();
            }
            dbRouter.clear();
        }
    }

    @Override
    public List<SkuProductEntity> querySkuProductEntityListByActivityId(Long activityId) {

        List<RaffleActivitySku> raffleActivitySkus = activitySkuDao.queryActivitySkuListByActivityId(activityId);
        List<SkuProductEntity> skuProductEntityList = new ArrayList<>(raffleActivitySkus.size());

        for (RaffleActivitySku raffleActivitySku : raffleActivitySkus) {
            //根据次数id去查找数据库表{raffle_activity_count}对应的次数配置
            RaffleActivityCount raffleActivityCount = activityCountDao.queryRaffleActivityCountByActivityCountId(raffleActivitySku.getActivityCountId());
            SkuProductEntity.ActivityCount activityCount = new SkuProductEntity.ActivityCount();
            activityCount.setTotalCount(raffleActivityCount.getTotalCount());
            activityCount.setDayCount(raffleActivityCount.getDayCount());
            activityCount.setMonthCount(raffleActivityCount.getMonthCount());

            skuProductEntityList.add(SkuProductEntity.builder().
                    activityCount(activityCount)
                    .activityCountId(raffleActivityCount.getActivityCountId()).
                    stockCount(raffleActivitySku.getStockCount())
                    .stockCountSurplus(raffleActivitySku.getStockCountSurplus())
                    .activityId(activityId)
                    .payAmount(raffleActivitySku.getPayAmount())
                    .sku(raffleActivitySku.getSku())
                    .build());
        }
        return skuProductEntityList;
    }

    @Override
    public UnpaidActivityOrderEntity queryUnpaidActivityOrder(SkuRechargeEntity skuRechargeEntity) {
        RaffleActivityOrder raffleActivityOrderReq = new RaffleActivityOrder();
        raffleActivityOrderReq.setUserId(skuRechargeEntity.getUserId());
        raffleActivityOrderReq.setSku(skuRechargeEntity.getSku());

        RaffleActivityOrder raffleActivityOrderRes =
                activityOrderDao.queryUnpaidActivityOrder(raffleActivityOrderReq);
        if (null == raffleActivityOrderRes) {
            return null;
        }
        return UnpaidActivityOrderEntity.builder()
                .outBusinessNo(raffleActivityOrderRes.getOutBusinessNo())
                .orderId(raffleActivityOrderRes.getOrderId())
                .userId(raffleActivityOrderRes.getUserId())
                .payAmount(raffleActivityOrderRes.getPayAmount())
                .build();
    }

    @Override
    public BigDecimal queryUserCreditAccountAmount(String userId) {

        try {
            dbRouter.doRouter(userId);

            UserCreditAccount userCreditAccountReq = new UserCreditAccount();
            userCreditAccountReq.setUserId(userId);
            UserCreditAccount userCreditAccount = userCreditAccountDao.queryUserCreditAccount(userCreditAccountReq);
            if (null == userCreditAccount) {
                return BigDecimal.ZERO;
            }
            return userCreditAccount.getAvailableAmount();
        } finally {
            dbRouter.clear();

        }
    }


}

